package handler

import (
	"context"
	"crypto/sha512"
	"fmt"
	"github.com/anaskhan96/go-password-encoder"
	"github.com/golang/protobuf/ptypes/empty"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	"gorm.io/gorm"
	. "service/usr_srv/global"
	"service/usr_srv/model"
	pt "service/usr_srv/proto"
	"strings"
	"time"
)

// 实现用户服务（service层）的所有接口
// 接口定义文件——user.proto

type UserServer struct{}

func ModelToResponse(user *model.User) *pt.UserInfoResponse {
	var birthday uint64
	if user.Birthday != nil {
		birthday = uint64((*user.Birthday).Unix())
	}
	return &pt.UserInfoResponse{
		Id:       user.ID,
		Password: user.Password,
		Mobile:   user.Mobile,
		NickName: user.NickName,
		Birthday: birthday,
		Gender:   user.Gender,
		Role:     int32(user.Role),
	}
}

// 优雅实现分页
func Paginate(page, pageSize int) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		if page == 0 {
			page = 1
		}

		switch {
		case pageSize > 100:
			pageSize = 100
		case pageSize <= 0:
			pageSize = 10
		}

		offset := (page - 1) * pageSize
		return db.Offset(offset).Limit(pageSize)
	}
}

// 获取用户列表
func (u *UserServer) GetUserList(c context.Context, pageInfo *pt.PageInfo) (*pt.UserListResponse, error) {
	// 获取用户列表
	var users []*model.User
	result := DB.Scopes(Paginate(int(pageInfo.Pn), int(pageInfo.PSize))).Find(&users)
	if result.Error != nil {
		return nil, result.Error
	}

	var resData []*pt.UserInfoResponse
	for _, user := range users {
		resData = append(resData, ModelToResponse(user))
	}
	return &pt.UserListResponse{
		Total: int32(result.RowsAffected),
		Data:  resData,
	}, nil
}

// 根据mobile查询用户
func (u *UserServer) GetUserByMobile(c context.Context, r *pt.MobileRequest) (*pt.UserInfoResponse, error) {
	user := new(model.User)
	// 以下两种查询方式都可以
	//DB.Where("mobile=?", r.Mobile).First(user)
	result := DB.Where(&model.User{Mobile: r.Mobile}).First(user)
	if result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "用户不存在")
	}
	if result.Error != nil {
		return nil, result.Error
	}
	return ModelToResponse(user), nil
}

// 根据id查询用户
func (u *UserServer) GetUserById(c context.Context, r *pt.IdRequest) (*pt.UserInfoResponse, error) {
	user := new(model.User)
	// 以下两种查询方式都可以
	//result:=DB.Where(&model.User{BaseModel: model.BaseModel{ID: r.Id}}, r.Id).First(user)
	result := DB.First(user, r.Id)
	if result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "用户%d不存在", r.Id)
	}
	if result.Error != nil {
		return nil, result.Error
	}
	return ModelToResponse(user), nil
}

// 创建用户的接口
func (u *UserServer) CreateUser(c context.Context, r *pt.CreateUserInfo) (*pt.UserInfoResponse, error) {
	user := new(model.User)
	result := DB.Where(&model.User{Mobile: r.Mobile}).First(user)
	if result.RowsAffected == 1 {
		return nil, status.Errorf(codes.AlreadyExists, "用户已存在")
	}

	options := &password.Options{16, 100, 32, sha512.New}
	salt, encodedPwd := password.Encode(r.Password, options)
	newPassword := fmt.Sprintf("$pbkdf2-sha512$%s$%s", salt, encodedPwd)

	user = &model.User{
		Mobile:   r.Mobile,
		Password: newPassword,
		NickName: r.NickName,
	}
	result = DB.Create(user)
	if result.Error != nil {
		return nil, status.Errorf(codes.Internal, result.Error.Error())
	}

	return ModelToResponse(user), nil
}

// 个人中心更新用户信息
func (u *UserServer) UpdateUser(c context.Context, r *pt.UpdateUserInfo) (*emptypb.Empty, error) {
	user := new(model.User)
	result := DB.First(user, r.Id)
	if result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "用户不存在")
	}

	// 时间戳转time.Time
	birthday := time.Unix(int64(r.Birthday), 0)

	// 以下两种更新方式都可以

	//user.NickName = r.NickName
	//user.Birthday = &birthday
	//user.Gender = r.Gender
	//result = DB.Save(user)

	result = DB.Model(user).Updates(map[string]interface{}{
		"nick_name": r.NickName,
		"birthday":  &birthday,
		"gender":    r.Gender,
	})
	if result.Error != nil {
		return nil, status.Errorf(codes.Internal, result.Error.Error())
	}
	return &empty.Empty{}, nil
}

func (u *UserServer) CheckPassword(c context.Context, r *pt.PasswordCheckInfo) (*pt.CheckResponse, error) {
	options := &password.Options{16, 100, 32, sha512.New}
	pwdInfo := strings.Split(r.EncryptedPassword, "$")
	return &pt.CheckResponse{
		Success: password.Verify(r.Password, pwdInfo[2], pwdInfo[3], options),
	}, nil
}
